home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Classes / XText / XText.m < prev    next >
Encoding:
Text File  |  1992-06-02  |  11.2 KB  |  450 lines

  1. /*    This file is part of the XText package (version 0.8)
  2.     Mike Dixon, April 1992
  3.     
  4.     Copyright (c) 1992 Xerox Corporation.  All rights reserved.
  5.  
  6.     Use and copying of this software and preparation of derivative works based
  7.     upon this software are permitted.  This software is made available AS IS,
  8.     and Xerox Corporation makes no warranty about the software or its
  9.     performance.
  10. */
  11.  
  12. #import "XText.h"
  13. #import <appkit/Application.h>
  14. #import <appkit/ClipView.h>
  15. #import <appkit/publicWraps.h>
  16. #import <stdio.h>
  17.  
  18. /*    Some of this code is based on other emacs-like Text classes by
  19.     Julie Zelenski, Lee Boynton, and Glen Diener.
  20.  
  21.     There's some ugly hair in here; the Text object is not very well
  22.     designed to support this kind of stuff.  No doubt this will all be
  23.     fixed by NextStep 9.0 ...
  24. */
  25.  
  26. @implementation XText
  27.  
  28. - initFrame:(const NXRect *)frameRect text:(const char *)theText
  29.     alignment:(int)mode
  30. {
  31.     // i don't understand why the compiler whines without the (char *) here
  32.     [super initFrame:frameRect text:(char *)theText alignment:mode];
  33.     posHint = -1;
  34.     xHintPos = -1;
  35.     return self;
  36. }
  37.  
  38. - goto:(int)pos end:(int)end mode:(int)mode
  39. {
  40.     int start;
  41.     
  42.     switch(mode) {
  43.  
  44.     case 0:        // move
  45.         [self setSel:pos :pos];
  46.         [self scrollSelToVisible];
  47.         posHint = -1;
  48.         break;
  49.  
  50.     case 1:        // delete
  51.     case 2:        // cut
  52.         if (pos != end) {
  53.             start = pos;
  54.             if (start > end)
  55.                 { start = end; end = pos; }
  56.             [self disableAutodisplay];
  57.             [self setSel:start :end];
  58.             if (mode == 1)
  59.                 [self delete:self];
  60.             else
  61.                 [self cut:self];
  62.         }
  63.         posHint = -1;
  64.         break;
  65.  
  66.     case 3:        // select
  67.         start = pos;
  68.         if (start > end)
  69.             { start = end; end = pos; }
  70.         // The Text object can't even extend the selection without flashing,
  71.         // unless we disable autodisplay
  72.         if (sp0.cp != spN.cp)
  73.             [self disableAutodisplay];
  74.         [self setSel:start :end];
  75.         posHint = pos;
  76.         break;
  77.     }
  78.     xHintPos = -1;
  79.     return self;
  80. }
  81.  
  82. - moveChar:(int)cnt mode:(int)mode
  83. {
  84.     int pos, end;
  85.     int max = [self textLength];
  86.     
  87.     if (sp0.cp == posHint) {
  88.         pos = sp0.cp + cnt;
  89.         end = spN.cp;
  90.     } else {
  91.         pos = spN.cp + cnt;
  92.         end = sp0.cp;
  93.     }
  94.     if (pos < 0)
  95.         pos = 0;
  96.     else if (pos > max)
  97.         pos = max;
  98.     return [self goto:pos end:end mode:mode];
  99. }
  100.  
  101. /* NXBGetc - a text stream macro to get the previous character. */
  102.  
  103. typedef struct {
  104.     id text;
  105.     NXTextBlock *block;
  106. } textInfo;
  107.  
  108. static char getPrevious(NXStream *s)
  109. {
  110.     textInfo *info = (textInfo *) s->info;
  111.     NXTextBlock *block = info->block->prior;
  112.  
  113.     if (!block)
  114.         return EOF;
  115.     s->buf_base = block->text;
  116.     s->buf_ptr = s->buf_base + block->chars;
  117.     s->offset -= block->chars;
  118.     info->block = block;
  119.     return *(--s->buf_ptr);
  120. }
  121.  
  122. #define NXBGetc(s) \
  123.     (((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \
  124.                                        *(--((s)->buf_ptr)) )
  125.  
  126. - moveWord:(int)cnt mode:(int)mode
  127. {
  128.     NXStream *s = [self stream];
  129.     char c;
  130.     int i, pos, end;
  131.     unsigned char digit_cat = charCategoryTable['0'];
  132.     unsigned char alpha_cat = charCategoryTable['a'];
  133.     unsigned char c_cat;
  134.     BOOL inWord = NO;
  135.  
  136.     if (cnt == 0)
  137.         return self;
  138.     if (sp0.cp == posHint) {
  139.         pos = sp0.cp;
  140.         end = spN.cp;
  141.     } else {
  142.         pos = spN.cp;
  143.         end = sp0.cp;
  144.     }
  145.     NXSeek(s, pos, NX_FROMSTART);
  146.     i = (cnt<0 ? -cnt : cnt);
  147.     while (1) {
  148.         c = (cnt<0 ? NXBGetc(s) : NXGetc(s));
  149.         if (c == EOF) break;
  150.         c_cat = charCategoryTable[c];
  151.         if (c_cat==alpha_cat || c_cat==digit_cat)
  152.             inWord = YES;
  153.         else if (inWord) {
  154.             --i;
  155.             if (i > 0)
  156.                 inWord = NO;
  157.             else
  158.                 break;
  159.         }
  160.     }
  161.     pos = NXTell(s);
  162.     if (c != EOF)
  163.         pos += (cnt<0 ? 1 : -1);
  164.     return [self goto:pos end:end mode:mode];
  165. }
  166.  
  167. /*  line is from an NXSelPt; returns the length of the line.
  168.     too bad this requires grunging in Text's data structures */
  169.  
  170. #define LINE_LENGTH(line) \
  171.     (self->theBreaks->breaks[(line)/sizeof(NXLineDesc)] & 0x3fff)
  172.  
  173. - moveLine:(int)cnt mode:(int)mode
  174. {
  175.     int pos, end, x, dir;
  176.  
  177.     if (sp0.cp == posHint) {
  178.         pos = sp0.cp;
  179.         end = spN.cp;
  180.     } else {
  181.         pos = spN.cp;
  182.         end = sp0.cp;
  183.     }
  184.     if (mode != 0)
  185.         [self disableAutodisplay];
  186.     // collapse and normalize the selection
  187.     [self setSel:pos :pos];
  188.     x = (sp0.cp == xHintPos ? xHint : (sp0.cp - sp0.c1st));
  189.     
  190.     if (cnt < 0) {
  191.         dir = NX_UP;
  192.         cnt = -cnt;
  193.     } else {
  194.         dir = NX_DOWN;
  195.     }
  196.     for (; cnt > 0; --cnt)
  197.         [self moveCaret: dir];
  198.  
  199.     pos = LINE_LENGTH(sp0.line)-1;
  200.     if (x < pos)
  201.         pos = x;
  202.     pos += sp0.c1st;
  203.     [self goto:pos end:end mode:mode];
  204.     xHintPos = pos;
  205.     xHint = x;
  206.     return self;
  207. }
  208.  
  209. - lineBegin:(int)mode
  210. {
  211.     int pos, end;
  212.  
  213.     if (sp0.cp == posHint) {
  214.         pos = sp0.c1st;
  215.         end = spN.cp;
  216.     } else {
  217.         pos = spN.c1st;
  218.         // Text is inconsistent about what line it thinks we're on
  219.         if (spN.cp == (spN.c1st + LINE_LENGTH(spN.line)))
  220.             pos = spN.cp;
  221.         end = sp0.cp;
  222.     }
  223.     return [self goto:pos end:end mode:mode];
  224. }
  225.  
  226. - lineEnd:(int)mode
  227. {
  228.     NXSelPt *sp;
  229.     int pos, end;
  230.  
  231.     if (sp0.cp == posHint) {
  232.         sp = &sp0;
  233.         end = spN.cp;
  234.     } else {
  235.         // need to correct for TBD
  236.         sp = &spN;
  237.         end = sp0.cp;
  238.     }
  239.     pos = sp->c1st + LINE_LENGTH(sp->line) - 1;
  240.     if (pos < sp->cp) {
  241.         // Text is being flakey again; we really want to be on the next line
  242.         // this is pretty gross
  243.         pos = sp->line;
  244.         if (theBreaks->breaks[pos/sizeof(NXLineDesc)] < 0)
  245.             pos += sizeof(NXHeightChange);
  246.         else
  247.             pos += sizeof(NXLineDesc);
  248.         pos = sp->cp + LINE_LENGTH(pos) - 1;
  249.     }
  250.     if ((pos == sp->cp) && (mode != 0))
  251.         ++pos;
  252.     return [self goto:pos end:end mode:mode];
  253. }
  254.  
  255. - docBegin:(int)mode
  256. {
  257.     return [self goto:0
  258.                  end:(sp0.cp == posHint ? spN.cp : sp0.cp)
  259.                  mode:mode];
  260. }
  261.  
  262. - docEnd:(int)mode
  263. {
  264.     return [self goto:[self textLength]
  265.                  end:(sp0.cp == posHint ? spN.cp : sp0.cp)
  266.                  mode:mode];
  267. }
  268.  
  269. - collapseSel:(int)dir
  270. {
  271.     int pos;
  272.  
  273.     if ((dir < 0) || ((dir == 0) && (sp0.cp == posHint)))
  274.         pos = sp0.cp;
  275.     else
  276.         pos = spN.cp;
  277.     return [self goto:pos end:pos mode:0];
  278. }
  279.  
  280. - transChars
  281. {
  282.     int pos = sp0.cp;
  283.     char buf[2], temp;
  284.  
  285.     if (pos == spN.cp) {
  286.         if (pos == (sp0.c1st + LINE_LENGTH(sp0.line) - 1))
  287.             --pos;
  288.         if (pos > 0)
  289.             if ([self getSubstring:buf start:pos-1 length:2] == 2) {
  290.                 temp = buf[1]; buf[1] = buf[0]; buf[0] = temp;
  291.                 [self disableAutodisplay];
  292.                 [self setSel:pos-1 :pos+1];
  293.                 [self replaceSel:buf length:2];
  294.                 return self;
  295.             }
  296.     }
  297.     NXBeep();
  298.     return self;
  299. }
  300.  
  301. - openLine
  302. {
  303.     int pos = sp0.cp;
  304.  
  305.     // don't do anything if there's a non-empty selection
  306.     if (pos == spN.cp) {
  307.         [self replaceSel:"\n"];
  308.         [self setSel:pos :pos];
  309.     } else
  310.         NXBeep();
  311.     return self;
  312. }
  313.  
  314. - scroll:(int)pages :(int)lines
  315. {
  316.     NXRect r;
  317.  
  318.     // if our superview isn't a ClipView, we can't scroll
  319.     if ([superview isKindOf:[ClipView class]]) {
  320.         [superview getBounds:&r];
  321.         r.origin.y += pages*r.size.height + lines*[self lineHeight];
  322.         [self scrollPoint:&r.origin];
  323.     } else
  324.         NXBeep();
  325.     return self;
  326. }
  327.  
  328. - scrollIfRO:(int)pages :(int)lines
  329. {
  330.     if (![self isEditable])
  331.         return [self scroll:pages :lines];
  332.     else
  333.         return nil;
  334. }
  335.  
  336. - insertChar:(NXEvent *)event
  337. {
  338.     char c;
  339.  
  340.     c = event->data.key.charCode;
  341.     [self replaceSel:&c length:1];
  342.     return self;
  343. }
  344.  
  345. - insertNextChar
  346. {
  347.     static id action = nil;
  348.  
  349.     if (!action)
  350.         action = [[XTEventMsgAction allocFromZone:[NXApp zone]]
  351.                             initSel:@selector(insertChar:)];
  352.     nextAction = action;
  353.     return self;
  354. }
  355.  
  356. @end
  357.  
  358. /*    A (not very elegant) table format for storing the initial emacs bindings.
  359.     Unused args are indicated by the magic value 99.
  360. */
  361.  
  362. typedef struct {
  363.     SEL   *sel;
  364.     short arg1;
  365.     short arg2;
  366.     keyCode key;
  367. } tbl_entry;
  368.  
  369. /*    For these and other key codes, refer to
  370.     /NextLibrary/Documentation/NextDev/Summaries/06_KeyInfo/KeyInfo.rtfd
  371. */
  372.  
  373. const tbl_entry emacs_base[] = {
  374. {&@selector(moveChar:mode:), -1,  0, 0x351},  // ctrl-b       move back char
  375. {&@selector(moveChar:mode:), -1,  1, 0x401},  // ctrl-h       delete back char
  376. {&@selector(moveChar:mode:), -1,  3, 0x353},  // ctrl-B       select back char
  377. {&@selector(moveChar:mode:),  1,  0, 0x3c1},  // ctrl-f       move fwd char
  378. {&@selector(moveChar:mode:),  1,  1, 0x3b1},  // ctrl-d       delete fwd char
  379. {&@selector(moveChar:mode:),  1,  3, 0x3c3},  // ctrl-F       select fwd char
  380. {&@selector(moveWord:mode:), -1,  0, 0x354},  // alt-b       move back word
  381. {&@selector(moveWord:mode:), -1,  1, 0x1b4},  // alt-del   delete back word
  382. {0,                              0,  0, 0x404},  // alt-h            (ditto)
  383. {&@selector(moveWord:mode:), -1,  3, 0x356},  // alt-B       select back word
  384. {&@selector(moveWord:mode:),  1,  0, 0x3c4},  // alt-f       move fwd word
  385. {&@selector(moveWord:mode:),  1,  1, 0x3b4},  // alt-d       delete fwd word
  386. {&@selector(moveWord:mode:),  1,  3, 0x3c6},  // alt-F       select fwd word
  387. {&@selector(moveLine:mode:), -1,  0, 0x081},  // ctrl-p       move back line
  388. {&@selector(moveLine:mode:), -1,  3, 0x083},  // ctrl-P       select back line
  389. {&@selector(moveLine:mode:),  1,  0, 0x371},  // ctrl-n       move fwd line
  390. {&@selector(moveLine:mode:),  1,  3, 0x373},  // ctrl-N       select fwd line
  391. {&@selector(lineBegin:),      0, 99, 0x391},  // ctrl-a       move to line begin
  392. {&@selector(lineBegin:),      3, 99, 0x393},  // ctrl-A       select to line bgn
  393. {&@selector(lineEnd:),          0, 99, 0x441},  // ctrl-e       move to line end
  394. {&@selector(lineEnd:),          1, 99, 0x3e1},  // ctrl-k       delete to line end
  395. {&@selector(lineEnd:),          3, 99, 0x443},  // ctrl-E       select to line end
  396. {&@selector(docBegin:),          0, 99, 0x2e6},  // alt-<       move to doc begin
  397. {&@selector(docEnd:),          0, 99, 0x2f6},  // alt->       move to doc begin
  398. {&@selector(collapseSel:),      0, 99, 0x381},  // ctrl-spc  collapse selection
  399. {&@selector(transChars),     99, 99, 0x481},  // ctrl-t    transpose chars
  400. {&@selector(setNextAction:),  0, 99, 0x421},  // ctrl-q       quote next key
  401. {&@selector(insertNextChar), 99, 99, 0x425},  // ctrl-alt-q   really quote key
  402. {&@selector(openLine),         99, 99, 0x071},  // ctrl-o       open line
  403. {&@selector(scroll::),          1, -1, 0x341},  // ctrl-v       scroll fwd page
  404. {0,                              0,  0, 0x0f6},  // alt-shft-down    (ditto)
  405. {&@selector(scroll::),         -1,  1, 0x344},  // alt-v       scroll back page
  406. {0,                              0,  0, 0x166},  // alt-shft-up    (ditto)
  407. {&@selector(scroll::),          0,  4, 0x343},  // ctrl-V       scroll fwd 4 lines
  408. {&@selector(scroll::),          0, -4, 0x346},  // alt-V       scroll back 4 lines
  409. {&@selector(scroll::),      -9999,  0, 0x165},  // alt-ctrl-up    scroll to start
  410. {&@selector(scroll::),       9999,  0, 0x0f5},  // alt-ctrl-down  scroll to end
  411. {&@selector(scrollIfRO::),      1, -1, 0x380},  // space       scroll fwd pg if RO
  412. {&@selector(scrollIfRO::),     -1,  1, 0x1b0},  // del       scroll back pg if RO
  413. {&@selector(scrollIfRO::),      0,  4, 0x382},  // shift-sp  scroll fwd 4 lines
  414. {&@selector(scrollIfRO::),      0, -4, 0x1b2},  // shift-del scroll back 4 lines
  415. {&@selector(scrollSelToVisible),
  416.                              99, 99, 0x2d1},  // ctrl-l       scroll to selection
  417. {0, 0, 0}
  418. };
  419.  
  420. void initbase_emacs(actionTbl actions, NXZone *zone)
  421. {
  422.     keyCode i;
  423.     const tbl_entry *e;
  424.     XTAction *a = [XTAction undefinedAction];
  425.  
  426.     // make all non-command control & alt combinations invoke "unboundKey"
  427.     for (i=0; i<KEY_CODES; i+=16) {
  428.         actions[i+1] = actions[i+3] = actions[i+4] = actions[i+5]
  429.             = actions[i+6] = actions[i+7] = a;
  430.     }
  431.  
  432.     // ... except for ctrl-i (a handy substitute for tab)
  433.     actions[6*16 + 1] = nil;
  434.  
  435.     // and then install the emacs key bindings
  436.     for (e=emacs_base; (e->key != 0); ++e) {
  437.         if (e->sel == 0) {}
  438.             // same action as previous binding
  439.         else if (e->arg1 == 99)
  440.             a = [[XTMsg0Action allocFromZone:zone] initSel:*(e->sel)];
  441.         else if (e->arg2 == 99)
  442.             a = [[XTMsg1Action allocFromZone:zone]
  443.                     initSel:*(e->sel) arg:e->arg1];
  444.         else
  445.             a = [[XTMsg2Action allocFromZone:zone]
  446.                     initSel:*(e->sel) arg:e->arg1 arg:e->arg2];
  447.         actions[e->key] = a;
  448.     }
  449. }
  450.